﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
#if SILVERLIGHT
using System.Windows;
using System.Windows.Media;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
#endif

namespace Microsoft.PlayerFramework
{
    public class TrackingPlugin : PluginBase
    {
        public TrackingPlugin()
        {
            TrackingEvents = new ObservableCollection<TrackingEventBase>();
        }

        /// <summary>
        /// The TimelineMarker ID used to store tracking events.
        /// </summary>
        public const string MarkerType_TrackingEvent = "TrackingEvent";

        /// <summary>
        /// Gets the total time watched in the current session. Reset when new media is loaded but not reset when media plays to the end, loops, or restarts.
        /// </summary>
        public TimeSpan TotalTimeWatched { get; private set; }

        /// <summary>
        /// Raised when a subscribed event occurs
        /// </summary>
        public event EventHandler<EventTrackedEventArgs> EventTracked;

        readonly Dictionary<string, PositionTrackingEvent> activeMarkers = new Dictionary<string, PositionTrackingEvent>();
        readonly List<PlaytimeTrackingEvent> spentPlaytimeEvents = new List<PlaytimeTrackingEvent>();

        DateTime? startTime;
        IList<TrackingEventBase> trackingEvents;

        /// <summary>
        /// Identifies the EvaluateOnForwardOnly dependency property.
        /// </summary>
        public static readonly DependencyProperty EvaluateOnForwardOnlyProperty = DependencyProperty.Register("EvaluateOnForwardOnly", typeof(bool), typeof(TrackingPlugin), new PropertyMetadata(true));

        /// <summary>
        /// Gets or sets whether seeking or scrubbing back in time can trigger ads. Set to false to allow ads to be played when seeking backwards. Default is true.
        /// </summary>
        public bool EvaluateOnForwardOnly
        {
            get { return (bool)GetValue(EvaluateOnForwardOnlyProperty); }
            set { SetValue(EvaluateOnForwardOnlyProperty, value); }
        }

        /// <summary>
        /// Gets the list of events being tracked.
        /// </summary>
        public IList<TrackingEventBase> TrackingEvents
        {
            get { return trackingEvents; }
            private set
            {
                if (trackingEvents != null)
                {
                    UnsubscribeAll();
                    if (trackingEvents is INotifyCollectionChanged)
                    {
                        ((INotifyCollectionChanged)trackingEvents).CollectionChanged -= TrackingPlugin_CollectionChanged;
                    }
                }
                trackingEvents = value;
                if (trackingEvents != null)
                {
                    if (MediaPlayer != null && MediaPlayer.PlayerState == PlayerState.Started) // make sure we're started. If not, wait until MediaStarted fires
                    {
                        SubscribeAll();
                    }
                    if (trackingEvents is INotifyCollectionChanged)
                    {
                        ((INotifyCollectionChanged)trackingEvents).CollectionChanged += TrackingPlugin_CollectionChanged;
                    }
                }
            }
        }

        void TrackingPlugin_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (MediaPlayer != null && MediaPlayer.PlayerState == PlayerState.Started) // make sure we're started. If not, wait until MediaStarted fires
            {
                if (e.OldItems != null)
                {
                    foreach (var item in e.OldItems.Cast<TrackingEventBase>())
                    {
                        Unsubscribe(item);
                    }
                }
                if (e.NewItems != null)
                {
                    foreach (var item in e.NewItems.Cast<TrackingEventBase>())
                    {
                        Subscribe(item);
                    }
                }
            }
        }

        /// <summary>
        /// Manually fires an event
        /// </summary>
        /// <param name="trackingEvent">The event to fire</param>
        /// <param name="seekedPast">Indicates that the marker was seeked past</param>
        protected void OnTrackEvent(TrackingEventBase trackingEvent, bool seekedPast)
        {
            if (EventTracked != null) EventTracked(this, new EventTrackedEventArgs(trackingEvent, seekedPast));
        }

        private void UnsubscribeAll()
        {
            foreach (var trackingEvent in trackingEvents)
            {
                Unsubscribe(trackingEvent);
            }
            TrackingEvents.Clear();
            activeMarkers.Clear();
        }

        private void SubscribeAll()
        {
            foreach (var trackingEvent in trackingEvents)
            {
                Subscribe(trackingEvent);
            }
        }

        void Subscribe(TrackingEventBase trackingEvent)
        {
            if (trackingEvent is PositionTrackingEvent)
            {
                var positionTrackingEvent = (PositionTrackingEvent)trackingEvent;
                if (!positionTrackingEvent.PositionPercentage.HasValue || (positionTrackingEvent.PositionPercentage.Value > 0 && positionTrackingEvent.PositionPercentage.Value < 1))
                {
                    var timelineMarker = new TimelineMarker();
                    timelineMarker.Type = MarkerType_TrackingEvent;
                    timelineMarker.Text = Guid.NewGuid().ToString();
                    if (positionTrackingEvent.PositionPercentage.HasValue)
                    {
                        timelineMarker.Time = TimeSpan.FromSeconds(positionTrackingEvent.PositionPercentage.Value * MediaPlayer.Duration.TotalSeconds);
                    }
                    else
                    {
                        timelineMarker.Time = positionTrackingEvent.Position;
                    }
                    MediaPlayer.Markers.Add(timelineMarker);
                    activeMarkers.Add(timelineMarker.Text, positionTrackingEvent);
                }
            }
        }

        void Unsubscribe(TrackingEventBase trackingEvent)
        {
            if (trackingEvent is PositionTrackingEvent)
            {
                var positionTrackingEvent = (PositionTrackingEvent)trackingEvent;
                if (!positionTrackingEvent.PositionPercentage.HasValue || (positionTrackingEvent.PositionPercentage.Value > 0 && positionTrackingEvent.PositionPercentage.Value < 1))
                {
                    var timelineMarkerKey = activeMarkers.First(kvp => kvp.Value == trackingEvent).Key;
                    var timelineMarker = MediaPlayer.Markers.FirstOrDefault(m => m.Type == MarkerType_TrackingEvent && m.Text == timelineMarkerKey);
                    MediaPlayer.Markers.Remove(timelineMarker);
                    activeMarkers.Remove(timelineMarkerKey);
                }
            }
        }

        /// <inheritdoc /> 
        protected override bool OnActivate()
        {
            MediaPlayer.UpdateCompleted += MediaPlayer_UpdateCompleted;
            MediaPlayer.MediaStarted += MediaPlayer_MediaStarted;
            MediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
            MediaPlayer.MarkerReached += MediaPlayer_MarkerReached;
            MediaPlayer.CurrentStateChanged += MediaPlayer_CurrentStateChanged;
            MediaPlayer.Seeked += MediaPlayer_Seeked;
            MediaPlayer.ScrubbingCompleted += MediaPlayer_ScrubbingCompleted;
            return true;
        }

        /// <inheritdoc /> 
        protected override void OnDeactivate()
        {
            MediaPlayer.UpdateCompleted -= MediaPlayer_UpdateCompleted;
            MediaPlayer.MediaStarted -= MediaPlayer_MediaStarted;
            MediaPlayer.MediaEnded -= MediaPlayer_MediaEnded;
            MediaPlayer.MarkerReached -= MediaPlayer_MarkerReached;
            MediaPlayer.CurrentStateChanged -= MediaPlayer_CurrentStateChanged;
            MediaPlayer.Seeked -= MediaPlayer_Seeked;
            MediaPlayer.ScrubbingCompleted -= MediaPlayer_ScrubbingCompleted;
        }

        /// <inheritdoc /> 
        protected override void OnUpdate()
        {
            UnsubscribeAll();
            trackingEvents = Tracking.GetTrackingEvents((DependencyObject)CurrentMediaSource);
            base.OnUpdate();
        }

        void MediaPlayer_MarkerReached(object sender, TimelineMarkerRoutedEventArgs e)
        {
            if (e.Marker.Type == MarkerType_TrackingEvent)
            {
                var positionTrackingEvent = activeMarkers[e.Marker.Text];
                OnTrackEvent(positionTrackingEvent, false);
            }
        }

        void MediaPlayer_MediaStarted(object sender, RoutedEventArgs e)
        {
            spentPlaytimeEvents.Clear();
            SubscribeAll();

            TotalTimeWatched = TimeSpan.Zero;
            if (!MediaPlayer.StartupPosition.HasValue)
            {
                foreach (var trackingEvent in TrackingEvents.OfType<PositionTrackingEvent>().Where(t => t.PositionPercentage.HasValue && t.PositionPercentage.Value == 0).ToList())
                {
                    OnTrackEvent(trackingEvent, false);
                }
            }
        }

        void MediaPlayer_MediaEnded(object sender, MediaPlayerActionEventArgs e)
        {
            foreach (var trackingEvent in TrackingEvents.OfType<PositionTrackingEvent>().Where(t => t.PositionPercentage.HasValue && t.PositionPercentage.Value == 1).ToList())
            {
                OnTrackEvent(trackingEvent, false);
            }
        }

        void MediaPlayer_Seeked(object sender, SeekRoutedEventArgs e)
        {
            EvaluateMarkers(e.PreviousPosition, e.Position, true);
        }

        void MediaPlayer_ScrubbingCompleted(object sender, ScrubProgressRoutedEventArgs e)
        {
            EvaluateMarkers(e.StartPosition, e.Position, true);
        }

        /// <summary>
        /// Evaluates all markers in a window and plays an ad if applicable.
        /// </summary>
        /// <param name="originalPosition">The window start position.</param>
        /// <param name="newPosition">The window end position. Note: This can be before originalPosition if going backwards.</param>
        public void EvaluateMarkers(TimeSpan originalPosition, TimeSpan newPosition, bool isSeeking)
        {
            if (!EvaluateOnForwardOnly || newPosition > originalPosition)
            {
                foreach (var marker in MediaPlayer.Markers.Where(m => m.Type == MarkerType_TrackingEvent).ToList())
                {
                    if (marker.Time <= newPosition && marker.Time > originalPosition)
                    {
                        var positionTrackingEvent = activeMarkers[marker.Text];
                        OnTrackEvent(positionTrackingEvent, isSeeking);
                    }
                }
            }
        }

        void MediaPlayer_CurrentStateChanged(object sender, RoutedEventArgs e)
        {
            switch (MediaPlayer.CurrentState)
            {
                case MediaElementState.Playing:
                    startTime = DateTime.Now.Subtract(TotalTimeWatched);
                    break;
                case MediaElementState.Closed:
                case MediaElementState.Opening:
                    startTime = null;
                    TotalTimeWatched = TimeSpan.Zero;
                    break;
                default:
                    if (startTime.HasValue) TotalTimeWatched = DateTime.Now.Subtract(startTime.Value);
                    break;
            }
        }

        void MediaPlayer_UpdateCompleted(object sender, RoutedEventArgs e)
        {
            if (MediaPlayer.CurrentState == MediaElementState.Playing && startTime.HasValue)
            {
                TotalTimeWatched = DateTime.Now.Subtract(startTime.Value);
                var percentageWatched = TotalTimeWatched.TotalSeconds / MediaPlayer.Duration.TotalSeconds;
                foreach (var playtimeTrackingEvent in TrackingEvents.OfType<PlaytimeTrackingEvent>().Except(spentPlaytimeEvents).ToList())
                {
                    if ((!playtimeTrackingEvent.PlaytimePercentage.HasValue && playtimeTrackingEvent.Playtime <= TotalTimeWatched) || (playtimeTrackingEvent.PlaytimePercentage.HasValue && playtimeTrackingEvent.PlaytimePercentage <= percentageWatched))
                    {
                        spentPlaytimeEvents.Add(playtimeTrackingEvent);
                        OnTrackEvent(playtimeTrackingEvent, false);
                    }
                }
            }
        }
    }

    public class TrackingEventBase
    {
        public object Data { get; set; }
    }

    public class PlaytimeTrackingEvent : TrackingEventBase
    {
        public TimeSpan Playtime { get; set; }
        public double? PlaytimePercentage { get; set; }
    }

    public class PositionTrackingEvent : TrackingEventBase
    {
        public TimeSpan Position { get; set; }
        public double? PositionPercentage { get; set; }
    }

    public class EventTrackedEventArgs : EventArgs
    {
        public EventTrackedEventArgs()
        {
            TimeStamp = DateTimeOffset.Now;
        }

        public EventTrackedEventArgs(TrackingEventBase trackingEvent)
            : this()
        {
            TrackingEvent = trackingEvent;
        }

        public EventTrackedEventArgs(TrackingEventBase trackingEvent, bool seekedPast)
            : this(trackingEvent)
        {
            SeekedPast = seekedPast;
        }

        public DateTimeOffset TimeStamp { get; set; }
        public TrackingEventBase TrackingEvent { get; set; }
        public bool SeekedPast { get; set; }
    }
}
